package bot {

import com.google.appengine.api.xmpp.JID;
import com.google.appengine.api.xmpp.Message;

import scala.collection.mutable.HashMap
import scala.collection._


/**
 * Implements the logic that handles all commands for the bot
 * 
 * @author jblom
 *
 */
object CommandHandler {
 	/**
 	 * Command skeleton class. Actual commands extend this class, set their name and help information
 	 * in the constructor, and implement process(...)
 	 */
	abstract class AbstractCommand(verb: String, shortHelp: String, usage: String, example: String) {
		private var this.verb: String = verb
		private var this.shortHelp: String = shortHelp
		private var this.usage: String = usage
		private var this.example: String = example;
		
		def getVerb() : String = {
			return verb;
		}
		
		def getShortHelp() : String = {
			return shortHelp;
		}

		def getUsage() : String = {
			return "Usage: " + usage;
		}

		def getExample() : String = {
			return "Example: " + example;
		}
		
		def process(words: Array[String], message: Message) : String;

	}
	
	private val commands: HashMap[String, AbstractCommand] = new HashMap[String, AbstractCommand];
	protected var fromJid: JID = null

	register(new HelpCommand())
	register(new EchoCommand())
	register(new RegisterCommand())
	register(new UnregisterCommand())
	register(new ListCommand())
	register(new WhoCommand())
	register(new WhoamiCommand())
	register(new WhoisCommand())
	register(new JoinCommand())
	register(new LeaveCommand())
	register(new NickCommand())
	register(new VersionCommand())
	register(new PrivateMessageCommand())
	register(new AddInfoCommand())
	register(new DelInfoCommand())
	register(new LastCommand())
	
	private def register(command: AbstractCommand) = {
		commands += command.getVerb() -> command
	}
	
	def handle(words: Array[String], message: Message) : String = {
		fromJid = message.getFromJid();
		
		var verb = words(0)
		if (verb.startsWith(")")) {
			verb = "/" + verb.substring(1);
		}
		commands.get(verb) match {
			case Some(command) => {
				return command.process(words, message)
			}
			case None => return "unimplemented"
		}
		return "ok"
	}
	
	private def getShortHelp(verb: String) : String = {
	  	commands.get(verb) match {
	  	  case Some(x) => return x.getShortHelp()
	  	  case None => return "";
	  	}
	}
 
	private def sendHelp(helpVerb: String) = {
		// make sure the verb starts with a slash or it won't be found
	    var verb = helpVerb
	    if (verb.startsWith(")")) {
	      verb = "/" + verb.substring(1)
	    }
		if (!verb.startsWith("/")) {
			verb = "/" + verb;
		}
		commands.get(verb) match {
		  	case Some(cmd) => {
				reply(cmd.getVerb() + " : " + cmd.getShortHelp())
				reply(cmd.getUsage())
				reply(cmd.getExample())
			}
		  	case None => reply("Help: unknow command '" + helpVerb + "'")
		}
 	}
	
	/*
	 * convenience method for sending replies from command implementations
	 */
	private def reply(msg: String) = {
		ConnectionHandler.sendMessage(fromJid, "_" + msg + "_", true)
	}
	
	private def replyIndent(msg: String) = {
		ConnectionHandler.sendMessage(fromJid, "         _" + msg + "_", true)
	}
	
	private class HelpCommand extends AbstractCommand("/help", "Shows list of available commands", "/help", "/help") {
		def process(words: Array[String], message: Message) : String = {
			if (words.length == 1) {
				showCommands();
			}
			else {
				sendHelp(words(1))
			}
			return "ok";
		}
		
		def showCommands() = {
			reply("The following commands are available:")
			commands foreach { cmd => { reply(cmd._1 + " : " + cmd._2.getShortHelp()) } }
			reply("Instead of '/', ')' may be used, e.g. )help")
			reply("Type '/help <command> to find out more about a command")
			reply("---")
		}
	}
	
	private class EchoCommand extends AbstractCommand("/echo", "Echoes a message back to you", "/echo <message>", "/echo Say this back to me") {
		def process(words: Array[String], message: Message) : String = {
			reply(message.getBody().substring(words(0).length() + 1))
			return "ok";
		}
	}

	private class PrivateMessageCommand extends AbstractCommand(
											"/msg", 
											"Sends a message to a specific user", 
											"/msg <nick> <message>", 
											"/msg slimmeRik Only you can see this") {
		def process(words: Array[String], message: Message) : String = {
			if (words.length > 2) {
				ConnectionHandler.processPrivateMessage(words(1), message, message.getBody().substring(words(0).length() + words(1).length() + 2))
			}
			else {
				reply(getUsage())
			}
			return "ok";
		}
	}

	private class VersionCommand extends AbstractCommand("/version", "Sends you some information about this chat bot", "/version", "/version") {
		def process(words: Array[String], message: Message) : String = {
			reply("Jan's chatbot, beta version 0.5f (" + commands.size + " commands)")
			reply("This chatbot is a Java/Scala hybrid Google App")
			return "ok";
		}
	}

	private class AddInfoCommand extends AbstractCommand(
											"/addinfo", 
											"Adds information about you that other users can see with /whois", 
											"/addinfo <keyword> <text>", 
											"/addinfo studie natuurwetenschappen") {
		def process(words: Array[String], message: Message) : String = {
			if (words.length > 2) {
				UserManager.addInfo(fromJid, words(1), message.getBody().substring(words(0).length() + words(1).length() + 2))
				reply("Information added")
			}
			else {
				reply(getUsage())
			}
			return "ok"
		}
	}

	private class DelInfoCommand extends AbstractCommand(
											"/delinfo", 
											"Deletes information about you", 
											"/delinfo <keyword>", 
											"/addinfo studie") {
		def process(words: Array[String], message: Message) : String = {
			if (words.length > 1) {
				val keyword = words(1)
				if (UserManager.getInfoByKeyword(fromJid, keyword) != null ) {
					UserManager.delInfo(fromJid, keyword)
					reply("Information deleted")
				}
				else {
					reply("There is no information for keyword '" + keyword + "'.")
				}
			}
			else {
				reply(getUsage())
			}
			return "ok"
		}
	}

	private class RegisterCommand extends AbstractCommand(
										"/register",
										"Registers you at the bot with the name that others will see",
										"/register [<nick>]",
										"/register slimmerik   - or -   /register") {
		
		def process(words: Array[String], message: Message) : String = {
			var nick: String = ""
			var user = UserManager.getUser(fromJid)
			if (user == null) {
				if (words.length > 1) {
					nick = words(1)
				}
				UserManager.addUser(fromJid, nick)
				nick = UserManager.getNick(fromJid.toString())
				// TODO: find out why nick isn't displayed when default (name part of email)
	    		reply(ChatUser.jid2email(fromJid) + " registered as '" + nick + "'")
			}
			else {
				reply("You are already registered.")
			}
			return "ok"
		}
	}
	
 	private class UnregisterCommand extends AbstractCommand("/unregister", "Removes your registration", "/unregister", "/unregister") {
		def process(words: Array[String], message: Message) : String = {
			UserManager.deleteUser(message.getFromJid())
			reply("Your registration has been removed. You will need to re-register if you want to use this chat bot again.")
			return "ok"
		}
 	}
  
	private class ListCommand extends AbstractCommand("/list", "Lists all registered users", "/list", "/list") {
		def process(words: Array[String], message: Message) : String = {
			//java.util.List<ChatUser> users = UserManager.getRegisteredUsers();
    		val users = scala.collection.JavaConversions.asBuffer(UserManager.getRegisteredUsers())
    		reply("Users registered: " + users.size)
    		users foreach { usr => reply(usr.getNick() + ": " + usr.getEmail() + " (" + (if (usr.isOnline()) "online" else "offline") + ")" )}
			return "ok";
		}
	}

	private class LastCommand extends AbstractCommand("/last", "Lists the last X active users", "/last [<X>]", "/last    - or -   /last 3") {
		def process(words: Array[String], message: Message) : String = {
    		val users = scala.collection.JavaConversions.asBuffer(UserManager.getLastActive(getLimit(words)))
    		reply("Most recently active users (" + users.size + "):")
    		users foreach { 
    			usr => reply(usr.getNick + ": " + usr.getEmail + 
    						 " (" + (if (usr.isOnline) "online" else "offline") + "): " +
    						 usr.getLastActiveFormatted)
    		}
			return "ok";
		}
		
		private def getLimit(words: Array[String]) : Int = {
			if (words.length > 1) {
				try {
					return words(1).toInt
				} catch {
					case _ => {
						reply(getUsage())
					}
				}
			}
			return 5
		}
	}

	private class WhoCommand extends AbstractCommand("/who", "Lists all users that are online", "/who", "/who") {
		def process(words: Array[String], message: Message) : String = {
			val users = scala.collection.JavaConversions.asBuffer(UserManager.getOnlineUsers())
    		reply("Users online: " + users.size);
			users foreach { usr => reply(usr.getNick() + ": " +  usr.getEmail())}
			return "ok";
		}
	}

	private class JoinCommand extends AbstractCommand("/join", "Makes you join the chat", "/join", "/join") {
		def process(words: Array[String], message: Message) : String = {
			UserManager.setOnline(message.getFromJid(), true, "joining");
    		reply("Welcome!");
			return "ok";
		}
	}

	private class LeaveCommand extends AbstractCommand("/leave", "Makes you leave the chat", "/leave", "/leave") {
		def process(words: Array[String], message: Message) : String = {
			UserManager.setOnline(message.getFromJid(), false, "leaving");
    		reply("Bye!");
			return "ok";
		}
	}
	
	private class WhoisCommand extends AbstractCommand("/whois", "Shows info about someone", "/whois <nick>", "/whois slimmeRik") {
		def process(words: Array[String], message: Message) : String = {
			if (words.length > 1) {
				val user = UserManager.getUserByNick(words(1))
				if (user != null) {
					reply("*" + user.getNick() + "* is " + user.getEmail() + " (" + (if (user.isOnline()) "online" else "offline") + ")")
					showInfo(user.getJid())
		    		showActivity(user)
				}
			    else {
			    	reply(words(1) + " is not known")
			    }
    		}
			else {
				reply(getUsage())
			}
			return "ok";
		}
	}
 
	private class WhoamiCommand extends AbstractCommand("/whoami", "Shows you who you are", "/whoami", "/whoami") {
		def process(words: Array[String], message: Message) : String = {
			val user = UserManager.getUser(fromJid)
    		reply("You are: *" + user.getNick() + "* (" + user.getEmail() + ") (" + (if (user.isOnline()) "online" else "offline") + ")")
    		showInfo(fromJid, true)
    		showActivity(user)
			return "ok";
		}
	}
 
	private class NickCommand extends AbstractCommand("/nick", "Changes your nick name", "/nick <nick name>", "/nick slimmeRik") {
		def process(words: Array[String], message: Message) : String = {
			if (words.length > 1) {
				val newNick = words(1)
				val status = UserManager.setNick(fromJid, newNick);
				if (status.equals("ok")) {
					reply("Your nick is now '" + newNick + "'");
				}
				else if (status.equals("inuse")) {
					reply("The nick '" + newNick + "' is already in use");    				
				}
				else {
					reply("Nick change to '" + newNick + "' failed");    				
				}
			}
			else {
				reply(getUsage());    			
			}
			return "ok";
		}
	}
 
	private def showInfo(jid: JID, all: Boolean = false) = {
		var info = UserManager.getInfo(jid)
		
		info.foreach(iv => {
			if (all || (!all && !iv._1.startsWith("."))) {
				replyIndent("*" + iv._1 +":* " + iv._2)
			}
		})
	}
	
	private def showActivity(user: ChatUser) = {
		replyIndent("Last active: " + user.getLastActiveFormatted())
		replyIndent("Last seen: " + user.getLastSeenFormatted())
	}
	
	def sendRegisterHelp(jid: JID) {
		fromJid = jid
		if (jid == null) {
			ConnectionHandler.logWarn("Hey! jid is null in sendRegisterHelp()")
		}
		else {
			CommandHandler.sendHelp("/register")
		}
	}

	private def restOfMessage(words: Array[String], message: Message, from: Int) : String = {
		var rest: String = ""
		
			//message.getBody().substring(words(0).length() + words(1).length() + 2)
		return rest
	}
}

} // package bot
